#include "../main.h"

#define CLASS Stream

#define DEFAULT_BUF_SIZE 100000

//#define JS_SET_RVAL(cx,vp,v)    (*(vp) = (v))


static JSBool	construct	(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
static void		finalize	(JSContext *cx, JSObject *obj);
static JSBool	getProperty	(JSContext *cx, JSObject *obj, jsval id, jsval *vp);


static JSBool Fopen(JSContext *cx, uintN argc, jsval *vp);
static JSBool Popen(JSContext *cx, uintN argc, jsval *vp);
static JSBool Fdopen(JSContext *cx, uintN argc, jsval *vp);

static JSClass __class = {
    "Stream",
    JSCLASS_HAS_PRIVATE,// | JSCLASS_CONSTRUCT_PROTOTYPE,
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, finalize,
    JSCLASS_NO_OPTIONAL_MEMBERS
};


enum types { UNDEFINED, FOPEN, POPEN, FDOPEN };
enum ids { PROP_PATH, PROP_SIZE };

struct T {
	int type;
	FILE *file;
	int fd;
};


static JSBool set(struct T *x, int type, FILE *f, int fd){
	x->type = type;
	x->file = f;
	x->fd = fd;
	trace1("stream", "setting internal struct: type=%i, fd=%i", x->type, x->fd);
	return JS_TRUE;
};

static void finalize(JSContext *cx, JSObject *obj){
	trace1("finalize");
	jsval rval;
//	Close(cx, obj, 0, NULL, &rval);
}



//////////////////////////////////////////////////////////////////////////////
//
static JSBool construct(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
	struct T *x = calloc(sizeof(struct T), 1);
	JS_SetPrivate(cx, obj, x);
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Close(JSContext *cx, uintN argc, jsval *vp) {
	trace1("close");
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	switch(x->type){
		case FOPEN:
			trace2("fclose");
			fclose(x->file);
			break;
		case POPEN:
			trace2("fclose");
			pclose(x->file);
			break;
		case 0:
			break;
		default:
			trace2("strange stream, dont know how to close");
			return JS_FALSE;
	}
	bzero(x, sizeof(struct T));
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Fdopen(JSContext *cx, uintN argc, jsval *vp) { 
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	int fd;
	char *flags = "r+";
	if(!JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "i/s", &fd, &flags))
		return JS_FALSE;
	trace1("fdopen", "fd: %i", fd);
	Close(cx, argc, vp);
	return  set(x, FDOPEN, fdopen(fd, flags), fd);
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Fopen(JSContext *cx, uintN argc, jsval *vp) { 
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	char *fn, *flags="w+";
	JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s/s", &fn, &flags);
	trace1("fopen", "filename: %s, flags: %s", fn, flags);
	Close(cx, argc, vp);	
	return set(x, FOPEN, fopen(fn, flags), 0);;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Popen(JSContext *cx, uintN argc, jsval *vp) { 
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	char *cmd, *flags="r";
	JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s/s", &cmd, &flags);
	trace1("popen", "cmd: %s, flags: %s", cmd, flags);
	Close(cx, argc, vp);
	return set(x, POPEN, popen(cmd, flags), 0);
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Write(JSContext *cx, uintN argc, jsval *vp) { 
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	trace1("write");
	
	char *text;	
	JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "s", &text);

	if (x->file)
		return fprintf(x->file, "%s", text), JS_TRUE;
	else if (x->fd)
		return write(x->fd,text,strlen(text)) >= 0 ? Close(cx, argc, vp) && JS_FALSE : JS_TRUE;

	return JS_FALSE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Read(JSContext *cx, uintN argc, jsval *vp) {
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	trace1("read");
	
	int max_bytes = DEFAULT_BUF_SIZE, len;
	char *buf, *b;

	JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "/i", &max_bytes);
	
	if (!(buf = (char *)malloc(max_bytes+10)))
		JS_ReportOutOfMemory(cx);

	jsrefcount rc = JS_SuspendRequest(cx);
	len = read(x->fd, buf, max_bytes);
	JS_ResumeRequest(cx, rc);
	if (len <= 0)
		return Close(cx, argc, vp) && JS_FALSE;
	
	//while((len = recv(fd, buf, max_bytes, MSG_DONTWAIT)) <= 0) usleep(100);

	b = buf+len;
	for (*(b--) = 0; (*b == '\n') || (*b == '\r'); *(b--) = 0);

	JS_SET_RVAL(cx, vp, atos(cx, buf));
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Eof(JSContext *cx, uintN argc, jsval *vp) {
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	trace1("eof");

	if (x->file)
		JS_SET_RVAL(cx, vp, feof(x->file) ? JSVAL_TRUE : JSVAL_FALSE);
		
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Flush(JSContext *cx, uintN argc, jsval *vp) {
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	trace1("flush");
	
	if (x->file) {
		trace2("fflush");
		fflush(x->file);
	} else {
		trace2("nothing to flush");
	}
	return JS_TRUE;
}


//////////////////////////////////////////////////////////////////////////////
//
static JSBool Gets(JSContext *cx, uintN argc, jsval *vp) {
	struct T *x = JS_GetPrivate(cx, JS_THIS_OBJECT(cx, vp));
	
	char *b, *buf;
	uint32 max_bytes = DEFAULT_BUF_SIZE;
	
	JS_ConvertArguments(cx, argc, JS_ARGV(cx, vp), "/u", &max_bytes);
//	trace1("gets", "max_bytes=%lu", max_bytes);
	
	if (!(buf = (char *)malloc(max_bytes+10))) {
		trace2("malloc() error");
		JS_ReportOutOfMemory(cx);
	}
	
	if (!fgets(buf, max_bytes, x->file)) {
		JS_SET_RVAL(cx, vp, JSVAL_FALSE);
		return JS_TRUE;
	}
	
	if (!*buf) {
		JS_SET_RVAL(cx, vp, JSVAL_FALSE);
		return JS_TRUE;
	}
		
	for (b = buf+(strlen(buf)-1); (*b == '\n') || (*b == '\r'); *(b--) = 0);
	JS_SET_RVAL(cx, vp, atos(cx, buf));
	return JS_TRUE;
}	


//////////////////////////////////////////////////////////////////////////////
//
static JSBool getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp){
	trace1("CLASS getProperty");
	trace2("id=%s", stoa(cx, id));
	if(!JSVAL_IS_INT(id))
		return JS_TRUE;
	switch(JSVAL_TO_INT(id)){
		case PROP_PATH:
			*vp = atos(cx, "/privet");
			break;
		case PROP_SIZE:
			break;
	}
	return JS_TRUE;
}


static JSPropertySpec props[] = {
	{"path",	PROP_PATH,	JSPROP_ENUMERATE, getProperty},
	{"size",	PROP_SIZE,	JSPROP_ENUMERATE, getProperty},
	{0}
};


static JSFunctionSpec methods[] = {
	JS_FN("fdopen", Fdopen, 2,0,0),
	JS_FN("fopen", 	Fopen, 	2,0,0),
	JS_FN("close", 	Close, 	1,0,0),
	JS_FN("popen", 	Popen, 	1,0,0),
	JS_FN("write", 	Write, 	2,0,0),
	JS_FN("read", 	Read, 	1,0,0),
	JS_FN("eof", 	Eof, 	1,0,0),
	JS_FN("flush", 	Flush, 	1,0,0),
	JS_FN("gets", 	Gets, 	1,0,0),
	JS_FS_END
};



//////////////////////////////////////////////////////////////////////////////
//
int module_stream_init(JSContext *cx, JSObject *obj){
	JSObject *proto = JS_InitClass(cx, global, NULL, 
		&__class, construct, 0,
		props, methods, NULL, NULL
	);
	return 0;
}


